package jbase.project.system.net.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import jbase.project.system.core.Sys;
import jbase.project.system.core.common.EventHook;
import jbase.project.system.exception.SysException;
import jbase.project.system.io.ContentLengthBlock;
import jbase.project.system.io.EndIdentificationBlock;
import jbase.project.system.net.ConnectChannel;
import jbase.project.system.util.HttpUtil;

public class HttpConnectChannel extends ConnectChannel implements HttpConstants {

	protected final static int STATUS_READ_RESPONSE_HEADER = 0x5;
	protected final static int STATUS_READ_RESPONSE_BODY = 0x6;
	protected final static int STATUS_READ_RESPONSE_LEAVE = 0xA;

	protected final static int STATUS_READ_RESPONSE_BODY_READ_CHUNKED_HEADER = 0x7;
	protected final static int STATUS_READ_RESPONSE_BODY_READ_CHUNKED_BODY = 0x8;
	protected final static int STATUS_READ_RESPONSE_BODY_READ_CHUNKED_TRAILER = 0xB;

	protected final static int STATUS_READ_RESPONSE_BODY_READ_CONTENT = 0x9;

	private ByteArrayOutputStream buffer = new ByteArrayOutputStream();

	private EventHook hook;

	public HttpConnectChannel(EventHook hook, String host) throws SysException {
		super(hook, host, 0);
		this.hook = hook;
	}

	public HttpConnectChannel(EventHook hook, String host, int timeout) throws SysException {
		super(hook, host, timeout);
		this.hook = hook;
	}

	public HttpConnectChannel(EventHook hook, String host, InputStream inputStream, OutputStream outputStream,
			boolean isAlive) {
		super(hook, host, inputStream, outputStream, isAlive);
		this.hook = hook;
	}

	public byte[] doReadResponse() throws SysException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ByteArrayOutputStream bodyData = new ByteArrayOutputStream();
		ByteArrayOutputStream headerData = new ByteArrayOutputStream();

		int dataLength = 0;

		boolean IS_CONTINUE = true;
		boolean IS_READ_LOOP = true;
		boolean IS_CHECK_BUFF = true;
		boolean IS_END_OF_MEDIA = false;

		int readCnt = 0;
		byte[] buff = new byte[1024 * 128];

		String headers = null;
		int contentLength = -1;
		int status = -1;
		String connection = null;

		int current_status = STATUS_READ_RESPONSE_HEADER;

		EndIdentificationBlock headerBlock = new EndIdentificationBlock(DBCRLF);

		ContentLengthBlock contentBlock = null;

		int transfer_status = 0x0;
		String transferEncoding = null;

		EndIdentificationBlock trunkHeaderBlock = null;
		ContentLengthBlock trunkBodyBlock = null;
		EndIdentificationBlock trunkTrailerBlock = null;

		Sys.setThreadVar("response.headerData", headerData);
		Sys.setThreadVar("response.bodyData", bodyData);
		Sys.setThreadVar("response.headerBlock", headerBlock);

		try {
			while (IS_CONTINUE) {
				while (IS_READ_LOOP) {
					if (IS_CHECK_BUFF) {
						byte[] bufferData = this.buffer.toByteArray();
						if (bufferData.length > 0) {
							readCnt = bufferData.length;
							for (int i = 0; i < bufferData.length; i++) {
								buff[i] = bufferData[i];
							}
							this.buffer = new ByteArrayOutputStream();
							IS_CHECK_BUFF = false;
						} else {
							IS_CHECK_BUFF = false;
							continue;
						}
					} else {
						readCnt = super.inputStream.read(buff);
					}
					if (readCnt == -1) {
						if (dataLength == 0) {
							reinit();
						} else {
							IS_READ_LOOP = false;
							IS_END_OF_MEDIA = true;
							continue;
						}
					}
					for (int i = 0; i < readCnt; i++) {
						dataLength++;
						byte currentByte = buff[i];
						switch (current_status) {
						case STATUS_READ_RESPONSE_HEADER:
							headerBlock.put(currentByte);
							if (headerBlock.haveComplete()) {
								headerData = headerBlock.getByteArrayOutputStream();
								// System.out.println("生成了headerData:" + new
								// String(headerData.toByteArray()));
								headerBlock = new EndIdentificationBlock(DBCRLF);

								headers = new String(headerData.toByteArray());
								contentLength = HttpUtil.getContentLength(headers);
								transferEncoding = HttpUtil.getResponseTransferEncoding(headers);
								this.hook.event("out",
										Sys.getThreadVar("host", "") + ":\nresponse<<-------\n" + headers);
								status = HttpUtil.getResponseStatus(headers);
								connection = HttpUtil.getResponseConnection(headers);

								Sys.setThreadVar("response.headers", headers);
								Sys.setThreadVar("response.contentLength", contentLength);
								Sys.setThreadVar("response.transferEncoding", transferEncoding);
								Sys.setThreadVar("response.status", status);

								if (contentLength > 0) {
									contentBlock = new ContentLengthBlock(contentLength);
									current_status = STATUS_READ_RESPONSE_BODY;
									transfer_status = STATUS_READ_RESPONSE_BODY_READ_CONTENT;
								} else if (contentLength == 0) {
									IS_READ_LOOP = false;
									current_status = STATUS_READ_RESPONSE_LEAVE;
								} else if (transferEncoding != null
										&& transferEncoding.trim().toUpperCase().equals("CHUNKED")) {
									current_status = STATUS_READ_RESPONSE_BODY;
									transfer_status = STATUS_READ_RESPONSE_BODY_READ_CHUNKED_HEADER;
									trunkHeaderBlock = new EndIdentificationBlock(CRLF);
								} else if (status == 304) {
									IS_READ_LOOP = false;
									current_status = STATUS_READ_RESPONSE_LEAVE;
								} else if (status == 302) {
									IS_READ_LOOP = false;
									current_status = STATUS_READ_RESPONSE_LEAVE;
								}
							}
							break;
						case STATUS_READ_RESPONSE_BODY:
							switch (transfer_status) {
							case STATUS_READ_RESPONSE_BODY_READ_CONTENT:
								contentBlock.put(currentByte);
								if (contentBlock.haveComplete()) {
									current_status = STATUS_READ_RESPONSE_LEAVE;
									IS_READ_LOOP = false;
									bodyData.write(contentBlock.getByteArrayOutputStream().toByteArray());
								}
								break;
							case STATUS_READ_RESPONSE_BODY_READ_CHUNKED_HEADER:
								trunkHeaderBlock.put(currentByte);
								if (trunkHeaderBlock.haveComplete()) {
									int currentTrunkBodySize = HttpUtil.calculateTrunkSize(
											new String(trunkHeaderBlock.getByteArrayOutputStream().toByteArray()));

									bodyData.write(trunkHeaderBlock.getByteArrayOutputStream().toByteArray());
									trunkBodyBlock = new ContentLengthBlock(currentTrunkBodySize + 2);
									transfer_status = STATUS_READ_RESPONSE_BODY_READ_CHUNKED_BODY;
									if (currentTrunkBodySize == 0) {
										trunkTrailerBlock = new EndIdentificationBlock(CRLF);
										transfer_status = STATUS_READ_RESPONSE_BODY_READ_CHUNKED_TRAILER;
									}
								}
								break;
							case STATUS_READ_RESPONSE_BODY_READ_CHUNKED_BODY:
								trunkBodyBlock.put(currentByte);
								if (trunkBodyBlock.haveComplete()) {
									transfer_status = STATUS_READ_RESPONSE_BODY_READ_CHUNKED_HEADER;
									trunkHeaderBlock = new EndIdentificationBlock(CRLF);
									bodyData.write(trunkBodyBlock.getByteArrayOutputStream().toByteArray());
								}
								break;
							case STATUS_READ_RESPONSE_BODY_READ_CHUNKED_TRAILER:
								trunkTrailerBlock.put(currentByte);
								if (trunkTrailerBlock.haveComplete()) {
									byte[] trailerBytes = trunkTrailerBlock.getByteArrayOutputStream().toByteArray();
									if (trailerBytes.length == CRLF.length) {
										bodyData.write(trailerBytes);
										current_status = STATUS_READ_RESPONSE_LEAVE;
										IS_READ_LOOP = false;
									} else {
										bodyData.write(trailerBytes);
										trunkTrailerBlock = new EndIdentificationBlock(CRLF);
									}
								}
								break;
							default:

								break;
							}
							break;
						case STATUS_READ_RESPONSE_LEAVE:
							buffer.write(new byte[] { currentByte });
							break;
						default:
							break;
						}
					}
				}
				if (IS_END_OF_MEDIA) {
					super.close();
					switch (current_status) {
					case STATUS_READ_RESPONSE_HEADER:
						if (headerBlock.length() > 0) {
							headers = new String(headerBlock.toString());
							headers = HttpUtil.buildHeader(headers);
							headerData = new ByteArrayOutputStream();
							headerData.write(headers.getBytes());
						}
						break;
					}
				}
				IS_CONTINUE = false;
			}
		} catch (IOException e) {
			super.close();
			hook.event("HttpConnectChannel.exception", e);
			throw new SysException(e);
		}

		if (connection != null && connection.trim().toUpperCase().equals("CLOSE")) {
			super.close();
		} else if (connection == null || !connection.trim().toUpperCase().equals("KEEP-ALIVE")) {
			super.close();
		}

		try {
			baos.write(headerData.toByteArray());
			baos.write(bodyData.toByteArray());
			baos.flush();
		} catch (IOException e) {
			hook.event("HttpConnectChannel.exception", e);
			throw new SysException(e);
		}
		return baos.toByteArray();
	}

	@Override
	public String toString() {
		return super.toString();
	}

}